{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "# Using BGE M3-Embedding Model with Milvus\n",
    "\n",
    "As Deep Neural Networks continue to advance rapidly, it's increasingly common to employ them for information representation and retrieval. Referred to as embedding models, they can encode information into dense or sparse vector representations within a multi-dimensional space.\n",
    "\n",
    "\n",
    "On January 30, 2024, a new member called BGE-M3 was released as part of the BGE model series. The M3 represents its capabilities in supporting over 100 languages, accommodating input lengths of up to 8192, and incorporating multiple functions such as dense, lexical, and multi-vec/colbert retrieval into a unified system. BGE-M3 holds the distinction of being the first embedding model to offer support for all three retrieval methods, resulting in achieving state-of-the-art performance on multi-lingual (MIRACL) and cross-lingual (MKQA) benchmark tests.\n",
    "\n",
    "Milvus, world's first open-source vector database, plays a vital role in semantic search with efficient storage and retrieval for vector embeddings. Its scalability and advanced functionalities, such as metadata filtering, further contribute to its significance in this field. \n",
    "\n",
    "This tutorial shows how to use **BGE M3 embedding model with Milvus** for semantic similarity search.\n",
    "\n",
    "![](../../images/bge_m3.png)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## Preparations\n",
    "\n",
    "We will demonstrate with `BAAI/bge-m3` model and Milvus in Standalone mode. The text for searching comes from the [M3 paper](https://arxiv.org/pdf/2402.03216.pdf). For each sentence in the paper, we use `BAAI/bge-m3` model to convert the text string into 1024 dimension vector embedding, and store each embedding in Milvus.\n",
    "\n",
    "We then search a query by converting the query text into a vector embedding, and perform vector Approximate Nearest Neighbor search to find the text strings with cloest semantic.\n",
    "\n",
    "To run this demo, be sure you have already [started up a Milvus instance](https://milvus.io/docs/install_standalone-docker.md) and installed python packages `pymilvus` (Milvus client library) and `FlagEmbedding` (library for BGE models)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "! pip install pymilvus FlagEmbedding"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Import packages."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "from pymilvus import (\n",
    "    connections,\n",
    "    utility,\n",
    "    FieldSchema,\n",
    "    CollectionSchema,\n",
    "    DataType,\n",
    "    Collection,\n",
    ")\n",
    "from FlagEmbedding import BGEM3FlagModel\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Set up the options for Milvus, specify model name as `BAAI/bge-m3`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "MILVUS_HOST = \"localhost\"\n",
    "MILVUS_PORT = \"19530\"\n",
    "COLLECTION_NAME = \"bge_m3_doc_collection\"  # Milvus collection name\n",
    "EMBEDDING_MODEL = \"BAAI/bge-m3\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "Let’s try the BGE M3 Embedding service with a text string, print the result vector embedding and get the dimensions of the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "0e74cb7d298d48b98dafb1e9b1dadee2",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Fetching 22 files:   0%|          | 0/22 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loading existing colbert_linear and sparse_linear---------\n",
      "----------using 4*GPUs----------\n",
      "[-0.03415   -0.04712   -0.0009007 -0.04697    0.04025   -0.07654\n",
      " -0.001877   0.007637  -0.01312   -0.007435  -0.0712     0.0526\n",
      "  0.02162   -0.04178    0.000628  -0.05307    0.00796   -0.0431\n",
      "  0.01224   -0.006145 ] ...\n",
      "\n",
      "Dimensions of `BAAI/bge-m3` embedding model is: 1024\n"
     ]
    }
   ],
   "source": [
    "test_sentences = \"What is BGE M3?\"\n",
    "\n",
    "model = BGEM3FlagModel(EMBEDDING_MODEL, use_fp16=True)  # Setting use_fp16 to True speeds up computation with a slight performance degradation\n",
    "\n",
    "\n",
    "test_embedding = model.encode([test_sentences])['dense_vecs'][0]\n",
    "\n",
    "print(f'{test_embedding[:20]} ...')\n",
    "dimension = len(test_embedding)\n",
    "print(f'\\nDimensions of `{EMBEDDING_MODEL}` embedding model is: {dimension}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## Load vectors to Milvus\n",
    "\n",
    "We need creat a collection in Milvus and build index so that we can efficiently search vectors. For more information on how to use Milvus, check out the [documentation](https://milvus.io/docs/example_code.md).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Status(code=0, message=)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Connect to Milvus\n",
    "connections.connect(host=MILVUS_HOST, port=MILVUS_PORT)\n",
    "\n",
    "# Remove collection if it already exists\n",
    "if utility.has_collection(COLLECTION_NAME):\n",
    "    utility.drop_collection(COLLECTION_NAME)\n",
    "\n",
    "# Set scheme with 3 fields: id (int), text (string), and embedding (float array).\n",
    "fields = [\n",
    "    FieldSchema(name=\"id\", dtype=DataType.INT64, is_primary=True, auto_id=False),\n",
    "    FieldSchema(name=\"text\", dtype=DataType.VARCHAR, max_length=65_535),\n",
    "    FieldSchema(name=\"embedding\", dtype=DataType.FLOAT_VECTOR, dim=dimension)\n",
    "]\n",
    "schema = CollectionSchema(fields, \"Here is description of this collection.\")\n",
    "# Create a collection with above schema.\n",
    "doc_collection = Collection(COLLECTION_NAME, schema)\n",
    "\n",
    "# Create an index for the collection.\n",
    "index = {\n",
    "    \"index_type\": \"IVF_FLAT\",\n",
    "    \"metric_type\": \"L2\",\n",
    "    \"params\": {\"nlist\": 128},\n",
    "}\n",
    "doc_collection.create_index(\"embedding\", index)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "Here we have prepared a data set of text strings from the [M3 paper](https://arxiv.org/pdf/2402.03216.pdf), named `m3_paper.txt`. It stores each sentence as a line, and we convert each line in the document into a dense vector embedding with `BAAI/bge-m3` and then insert these embeddings into Milvus collection."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "with open('./docs/m3_paper.txt', 'r') as f:\n",
    "    lines = f.readlines()\n",
    "\n",
    "embeddings = model.encode(lines)['dense_vecs']\n",
    "entities = [\n",
    "    list(range(len(lines))),  # field id (primary key) \n",
    "    lines,  # field text\n",
    "    embeddings,  #field embedding\n",
    "]\n",
    "insert_result = doc_collection.insert(entities)\n",
    "\n",
    "# In Milvus, it's a best practice to call flush() after all vectors are inserted,\n",
    "# so that a more efficient index is built for the just inserted vectors.\n",
    "doc_collection.flush()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## Query\n",
    "\n",
    "Here we will build a `semantic_search` function, which is used to retrieve the topK most semantically similar document from a Milvus collection.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# Load the collection into memory for searching\n",
    "doc_collection.load()\n",
    "\n",
    "\n",
    "def semantic_search(query, top_k=3):\n",
    "    vectors_to_search = model.encode([query])['dense_vecs']\n",
    "    search_params = {\n",
    "        \"metric_type\": \"L2\",\n",
    "        \"params\": {\"nprobe\": 10},\n",
    "    }\n",
    "    result = doc_collection.search(vectors_to_search, \"embedding\", search_params, limit=top_k, output_fields=[\"text\"])\n",
    "    return result[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "Here we ask a question about the embedding models."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "distance = 0.46\n",
      "Particularly, M3-Embedding is proficient in multilinguality, which is able to support more than 100 world languages.\n",
      "\n",
      "distance = 0.53\n",
      "1) We present M3-Embedding, which is the first model which supports multi-linguality, multifunctionality, and multi-granularity.\n",
      "\n",
      "distance = 0.63\n",
      "In this paper, we present M3-Embedding, which achieves notable versatility in supporting multilingual retrieval, handling input of diverse granularities, and unifying different retrieval functionalities.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "question = 'How many working languages does the M3-Embedding model support?'\n",
    "\n",
    "match_results = semantic_search(question, top_k=3)\n",
    "for match in match_results:\n",
    "    print(f\"distance = {match.distance:.2f}\\n{match.entity.text}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "The smaller the distance, the closer the vector is, that is, semantically more similar. We can see that the top 1 result returned *\"M3-Embedding...more than 100 world languages...\"* can directly answer the question.\n",
    "\n",
    "Let's try another question."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "distance = 0.61\n",
      "The three data sources complement to each other, which are applied to different stages of the training process.\n",
      "\n",
      "distance = 0.69\n",
      "Our dataset consists of three sources: 1) the extraction of unsupervised data from massive multi-lingual corpora, 2) the integration of closely related supervised data, 3) the synthesization of scarce training data.\n",
      "\n",
      "distance = 0.74\n",
      "In this   Table 1: Specification of training data.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "question = 'What are the sources of data used in the training dataset?'\n",
    "\n",
    "match_results = semantic_search(question, top_k=3)\n",
    "for match in match_results:\n",
    "    print(f\"distance = {match.distance:.2f}\\n{match.entity.text}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "In this example, the top 2 results have enough information to answer the question. By selecting the top K results, semantic search with embedding model and vector retrieval is able to identify the meaning of queries and return the most semantically similar documents. Plugging this solution with Large Language Model (a pattern referred to as Retrieval Augmented Generation), a more human-readable answer can be crafted.\n",
    "\n",
    "We can delete this collection to save resources."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# Drops the collection\n",
    "utility.drop_collection(COLLECTION_NAME)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this notebook, we showed how to generate dense vectors with BGE M3 embedding model and use Milvus to perform semantic search. In the upcoming releases, Milvus will support hybrid search with dense and sparse vectors, which BGE M3 model can produce at the same time.\n",
    "\n",
    "Milvus has integrated with all major model providers, including OpenAI, HuggingFace and many more. You can learn about Milvus at https://milvus.io/docs."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
